package com.ice.ucenter.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author : xiajun
 * redis分布式锁
 */
@Slf4j
@Service
public class RedisLock {

    @Autowired
    private RedisTemplate redisTemplate;

    public static final String REDIS_LOCK = "RedisLock:";


    /**
     * 5s 加锁超时时间，单位秒， 即：加锁时间内执行完操作，如果未完成会有并发现象
     */
    private static final long DEFAULT_WAIT_LOCK_TIME_OUT = 5;
    /**
     * 15s锁的有效时间
     */
    private static final long DEFAULT_EXPIRE = 15;

    /**
     * 获取锁
     *
     * @param key
     * @return : boolean
     * @author :
     */
    public boolean lock(String key) {
        return lock(key, DEFAULT_WAIT_LOCK_TIME_OUT, TimeUnit.SECONDS);
    }

    /**
     * 释放锁
     *
     * @param key
     * @return : void
     * @author :
     */
    public void unlock(String key) {
        RedisConnection connection = null;
        try {
            String lockKey = generateLockKey(key);
            connection = redisTemplate.getConnectionFactory().getConnection();
            // 释放锁
            connection.del(lockKey.getBytes());
        } catch (Exception e) {
            log.error("{}", e.getMessage(), e);
        } finally {
            //一定要关闭连接，不然会导致连接一直不释放
            connection.close();
        }
    }

    /**
     * 因为在系统中key一般都有前缀，组装锁的key
     *
     * @param key
     * @return : java.lang.String
     * @author :
     */
    private String generateLockKey(String key) {
        return String.format(REDIS_LOCK + "%s", key);
    }

    /**
     * 获取锁
     *
     * @param key
     * @param timeout
     * @param seconds
     * @return : boolean
     * @author :
     */
    public boolean lock(String key, long timeout, TimeUnit seconds) {
        //获取key
        String lockKey = generateLockKey(key);
        //换成格林威治时间
        long nanoWaitForLock = seconds.toNanos(timeout);
        //返回最准确的系统计时器的当前值，以毫微秒为单位
        long start = System.nanoTime();
        RedisConnection connection = null;
        try {
            //连接 Redis
            connection = redisTemplate.getConnectionFactory().getConnection();

            while ((System.nanoTime() - start) < nanoWaitForLock) {
                // 用SETNX命令实现分布式锁
                if (connection.setNX(lockKey.getBytes(), new byte[0])) {
                    //暂设置为80s过期，防止异常中断锁未释放
                    redisTemplate.expire(lockKey, DEFAULT_EXPIRE, TimeUnit.SECONDS);
                    if (log.isDebugEnabled()) {
                        log.debug("add RedisLock[{}].{}", key, Thread.currentThread());
                    }
                    return true;
                }
                //加随机时间防止活锁
                TimeUnit.MILLISECONDS.sleep(1000 + new Random().nextInt(100));
            }
        } catch (Exception e) {
            log.error("{}", e.getMessage(), e);
            unlock(key);
        } finally {
            //一定要关闭连接，不然会导致连接一直不释放
            connection.close();
        }
        return false;
    }

    public boolean tryLockOnce(String key) {
        //暂设置为50s过期，防止异常中断锁未释放
        return tryLockOnce(key, DEFAULT_EXPIRE, TimeUnit.SECONDS);
    }

    public boolean tryLockOnce(String key, long timeout, TimeUnit seconds) {
        String lockKey = generateLockKey(key);
        RedisConnection connection = null;
        try {
            connection = redisTemplate.getConnectionFactory().getConnection();
            if (connection.setNX(lockKey.getBytes(), new byte[0])) {
                redisTemplate.expire(lockKey, timeout, seconds);
                if (log.isDebugEnabled()) {
                    log.debug("add RedisLock[{}].{}", key, Thread.currentThread());
                }
                return true;
            }
        } catch (Exception e) {
            log.error("{}", e.getMessage(), e);
            unlock(key);
        } finally {
            //一定要关闭连接，不然会导致连接一直不释放
            connection.close();
        }
        return false;
    }

    public boolean isLocked(String key) {
        return redisTemplate.hasKey(key);
    }


}
